package com.lishui.waterfriend.service.impl;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.lishui.waterfriend.common.result.PageResult;
import com.lishui.waterfriend.contant.MessageConstant;
import com.lishui.waterfriend.contant.UserConstant;
import com.lishui.waterfriend.context.BaseContext;
import com.lishui.waterfriend.exception.*;
import com.lishui.waterfriend.mapper.UserTeamMapper;
import com.lishui.waterfriend.pojo.dto.PageDTO;
import com.lishui.waterfriend.pojo.dto.UserRegisterDTO;
import com.lishui.waterfriend.pojo.entity.User;
import com.lishui.waterfriend.pojo.entity.UserTeam;
import com.lishui.waterfriend.pojo.vo.UserVO;
import com.lishui.waterfriend.service.UserService;
import com.lishui.waterfriend.mapper.UserMapper;
import com.lishui.waterfriend.utils.LevenshteinDistanceUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.lishui.waterfriend.contant.UserConstant.USER_LOGIN_STATE;

/**
 * 用户服务实现类
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {
    private final Integer MATCH_NUM = 20;

    @Resource
    private UserMapper userMapper;

    @Resource
    private UserTeamMapper userTeamMapper;

    /**
     * 盐值，混淆密码
     */
    private static final String SALT = "lishui";

    /**
     * 用户注册
     *
     * @return 新用户 id
     */
    @Override
    public long userRegister(UserRegisterDTO userRegisterDTO) {
        String username = userRegisterDTO.getUsername();
        String userAccount = userRegisterDTO.getUserAccount();
        String userPassword = userRegisterDTO.getUserPassword();
        String planetCode = userRegisterDTO.getPlanetCode();
        String checkPassword = userRegisterDTO.getCheckPassword();
        // 1. 校验
        if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword, planetCode, username)) {
            throw new ParameterException(MessageConstant.EMPTY_PARAMETER);
        }
        if (userAccount.length() < 4) {
            throw new ParameterException(MessageConstant.USER_ACCOUNT_SHORT);
        }
        if (userPassword.length() < 8) {
            throw new ParameterException(MessageConstant.USER_PASSWORD_SHORT);
        }
        if (planetCode.length() > 5) {
            throw new ParameterException(MessageConstant.PLANET_NUMBER_LONG);
        }
        // 账户不能包含特殊字符
        String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？]";
        Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
        if (matcher.find()) {
            throw new ParameterException(MessageConstant.CONTAINS_SPECIAL_CHARACTERS);
        }
        // 密码和校验密码不相同
        if (!userPassword.equals(checkPassword)) {
            throw new ParameterException(MessageConstant.PASSWORD_DIFFER_CODE);
        }

        // 2. 加密
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        // 3. 插入数据
        User user = new User();
        user.setAvatarUrl("https://lishui-bucket.oss-cn-beijing.aliyuncs.com/avatar/default.jpg");
        user.setUserAccount(userAccount);
        user.setUserPassword(encryptPassword);
        user.setPlanetCode(planetCode);
        user.setUsername(username);
        boolean saveResult = this.save(user);
        if (!saveResult) {
            throw new ParameterException(MessageConstant.REGISTER_FAILED);
        }
        return user.getId();
    }

    /**
     * 用户登录
     *
     * @param userAccount  用户账户
     * @param userPassword 用户密码
     * @param request
     * @return 脱敏后的用户信息
     */
    @Override
    public UserVO userLogin(String userAccount, String userPassword, HttpServletRequest request) {
        // 1. 校验
        if (StringUtils.isAnyBlank(userAccount, userPassword)) {
            throw new ParameterException(MessageConstant.EMPTY_PARAMETER);
        }
        if (userAccount.length() < 4) {
            throw new ParameterException(MessageConstant.USER_ACCOUNT_SHORT);
        }
        if (userPassword.length() < 8) {
            throw new ParameterException(MessageConstant.USER_PASSWORD_SHORT);
        }
        // 账户不能包含特殊字符
        String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？]";
        Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
        if (matcher.find()) {
            throw new ParameterException(MessageConstant.CONTAINS_SPECIAL_CHARACTERS);
        }
        // 2. 加密
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        // 查询用户是否存在
        QueryWrapper<User> queryWrapper1 = new QueryWrapper<>();
        queryWrapper1.eq("user_account", userAccount);
        Long count = userMapper.selectCount(queryWrapper1);
        // 用户不存在
        if (count == 0) {
            throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
        }
        // 查询密码是否正确
        QueryWrapper<User> queryWrapper2 = new QueryWrapper<>();
        queryWrapper2.eq("user_account", userAccount);
        queryWrapper2.eq("user_password", encryptPassword);
        User user = userMapper.selectOne(queryWrapper2);
        // 密码错误
        if (user == null) {
            throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
        }
        // 3. 用户脱敏
        // User safetyUser = getSafetyUser(user);
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user, userVO);
        // 4. 记录用户的登录态，用userId记录登录态
        request.getSession().setAttribute(USER_LOGIN_STATE, userVO.getId());
        return userVO;
    }

    /**
     * 用户退出登录
     *
     * @param request
     */
    @Override
    public void userLogout(HttpServletRequest request) {
        // 移除登录态
        request.getSession().removeAttribute(USER_LOGIN_STATE);
    }

    @Override
    public List<UserVO> searchUsers(String username) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        if (StringUtils.isNotBlank(username)) {
            queryWrapper.like("username", username);
        }
        List<User> userList = userMapper.selectList(queryWrapper);
        return userList.stream().map(user -> {
            UserVO userVO = new UserVO();
            BeanUtils.copyProperties(user, userVO);
            return userVO;
        }).collect(Collectors.toList());
    }

    /**
     * 完全SQL，根据标签列表查用户，分页。
     * @param tagList
     * @return
     */
    @Override
    public PageResult searchUserByTagListSql(List<String> tagList, Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        for (String tag : tagList) {
            queryWrapper = queryWrapper.like("tags", tag);
        }
        List<User> userList = userMapper.selectList(queryWrapper);
        PageInfo<User> pageInfo = new PageInfo<>(userList);
        return new PageResult(pageInfo.getTotal(), userList.stream().map(user -> {
            UserVO userVO = new UserVO();
            BeanUtils.copyProperties(user, userVO);
            return userVO;
        }).collect(Collectors.toList()));
    }

    /**
     * 先将所有用户查到内存中，再根据标签挑选合适的User。不分页，这里分页不是很方便
     * @param tagList
     * @return
     */
    @Override
    public List<UserVO> searchUserByTagListMemory(List<String> tagList) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        List<User> userList = userMapper.selectList(queryWrapper);
        Gson gson = new Gson();
        return userList.stream().filter(user -> {
            String userTagsString = user.getTags();
            Set<String> userTagsSet = gson.fromJson(userTagsString, new TypeToken<Set<String>>(){}.getType());
            for (String tag : tagList) {
                if (!userTagsSet.contains(tag)) return false;
            }
            return true;
        }).map(user -> {
            UserVO userVO = new UserVO();
            BeanUtils.copyProperties(user, userVO);
            return userVO;
        }).collect(Collectors.toList());
    }

    /**
     * 是否为管理员
     * @return
     */
    public boolean isAdmin() {
        // 仅管理员可查询
        Long userId = BaseContext.getCurrentId();
        User user = this.getById(userId);
        return user != null && user.getUserRole() == UserConstant.ADMIN_ROLE;
    }

    @Override
    public void update(User user) {
        Long nowUserId = user.getId();
        if (nowUserId == null) throw new ParameterException(MessageConstant.USER_ID_EXCEPTION);
        // 鉴定权限，当为 管理员用户 或 当前用户 时允许修改
        Long sessionUserId = BaseContext.getCurrentId();
        if(!Objects.equals(sessionUserId, nowUserId) && !isAdmin()) {
            throw new UserRightsException(MessageConstant.MISSING_ADMINISTRATOR_RIGHTS);
        }
        String tags = user.getTags();
        List<String> tagList = JSONUtil.toList(tags, String.class);
        Collections.sort(tagList);
        tags = JSONUtil.toJsonStr(tagList);
        user.setTags(tags);
        this.updateById(user);
    }

    @Override
    public PageResult PageQueryRecommendUser(Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        List<User> userList =  this.list(queryWrapper);
        PageInfo<User> pageInfo = new PageInfo<>(userList);
        return new PageResult(pageInfo.getTotal(), userList.stream().map(user -> {
            UserVO userVO = new UserVO();
            BeanUtils.copyProperties(user, userVO);
            return userVO;
        }).collect(Collectors.toList()));
    }

    @Override
    @Transactional
    public boolean removeUserById(long id) {
        this.removeById(id);
        QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id", id);
        userTeamMapper.delete(queryWrapper);
        return true;
    }

    @Override
    public PageResult PageQueryMatchUser(PageDTO pageDTO) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        // 先查需要的部分，优化查询速度
        queryWrapper.select("id", "tags");
        queryWrapper.isNotNull("tags");
        List<User> userList = this.list(queryWrapper);
        User currentUser = this.getById(BaseContext.getCurrentId());
        String tags = currentUser.getTags();
        Gson gson = new Gson();
        // 存放用户匹配度结果列表
        List<Pair<User, Integer>> pairList = new ArrayList<>();
        // 当前用户的标签列表
        List<String> tagList = gson.fromJson(tags, new TypeToken<List<String>>() {}.getType());
        // 依次计算当前用户和其他用户的相似度
        for (int i = 0; i < userList.size(); i ++ ) {
            User user = userList.get(i);
            String userTags = user.getTags();
            // 排除无标签用户和自己
            if (StringUtils.isBlank(userTags) || user.getId() == currentUser.getId()) {
                continue;
            }
            List<String> userTagList = gson.fromJson(userTags, new TypeToken<List<String>>() {}.getType());
            // 计算分数
            Integer distance = LevenshteinDistanceUtil.minDistance(tagList, userTagList);
            pairList.add(Pair.of(user, distance));
        }
        // 按编辑距离由小到大排序
        List<Pair<User, Integer>> topUserPairList = pairList.stream()
                .sorted((a, b) -> (int) (a.getValue() - b.getValue()))
                .limit(this.MATCH_NUM)
                .collect(Collectors.toList());
        // 原本顺序的 userId
        List<Long> topUserIdList = topUserPairList.stream()
                .map(pair -> pair.getKey().getId())
                .collect(Collectors.toList());
        // // 打印验证
        // topUserPairList.forEach(pair -> {
        //     log.info("UserId: {}, 相似度: {}", pair.getKey().getId(), pair.getValue());
        // });
        // 根据这些Id查数据库或玩完整的用户信息，但是会打乱顺序
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.in("id", topUserIdList);
        // 1(userId) => User1, 2(userId) => User2，虽说分组查出的是 id : 集合，但是集合中只有一个元素
        Map<Long, List<UserVO>> userIdListMap = this.list(userQueryWrapper)
                .stream()
                .map(user -> {
                    UserVO userVO = new UserVO();
                    BeanUtils.copyProperties(user, userVO);
                    return userVO;
                })
                .collect(Collectors.groupingBy(UserVO::getId));
        List<UserVO> finalUserList = new ArrayList<>();
        for (Long userId : topUserIdList) {
            finalUserList.add(userIdListMap.get(userId).get(0));
        }
        Integer pageNum = pageDTO.getPageNum();
        Integer pageSize = pageDTO.getPageSize();
        Integer startIndex = (pageNum - 1) * pageSize;
        Integer endIndex = Math.min(startIndex + pageSize, finalUserList.size());
        List<UserVO> pageResultList = finalUserList.subList(startIndex, endIndex);
        return new PageResult(finalUserList.size(), pageResultList);
    }

    @Override
    public UserVO getCurrentUser() {
        User user = this.getById(BaseContext.getCurrentId());
        if (user == null) {
            throw new PermissionException(MessageConstant.USER_NOT_LOGIN);
        }
        UserVO currentUser = new UserVO();
        BeanUtils.copyProperties(user, currentUser);
        return currentUser;
    }
}
